import java.io.*;
import java.net.*;
import java.util.*;
import messaging.*;
import debug.*;
import java.util.concurrent.Semaphore;

public class PrimeServer extends Server
{
	private int numClients;
	private final int maxPrime;
	volatile private int lastPrime;
	private int maxClients;
	//lockHolder keeps track of which client is in the HashTable update section; nulls implies lock is free.
	private Connection lockHolder = null;
	private ArrayList<SocketThread> clientList;
	private Integer clientCommits = new Integer(0);
	final TreeSet<Integer> result = new TreeSet<Integer>();
	
	// power stuff
	private long totalClientTime;
	private long totalEnergy;
	private long totalIterations;
	private long missedDeadlines;
	private long totalJobs;
	private final int deadlineFactor;
	//private long deadline;
	private static Object powerLock = new Object();
	
	private static long startTime = 0;
	
	volatile private static Semaphore semLock = new Semaphore(1, true);
	
	public PrimeServer(int maxPrime, int maxClients, int deadlineFactor) throws IOException
	{
		super();
		this.maxPrime = maxPrime;
		this.maxClients = maxClients;
		this.deadlineFactor = deadlineFactor;
		lastPrime = 2;
		numClients = 0;
		//This collection should be backed by the TreeMap according to the JavaAPI,
		//Thus anything added to the clients will be reflected by this collection.
		clientList = new ArrayList<SocketThread>();
		
		totalClientTime = 0;
		totalEnergy = 0;
		totalIterations = 0;
		
		//Turn debug output off for this server.
		Debug.setDebug(false);
	}
	@Override
	public void receive(Message msg)
	{
		switch(msg.getType())
		{
			case UPDATE:
				grantLock(msg);
				break;
			//When they release a lock, dispatch a new job to the client
			case RELEASE:
				releaseLock();
				dispatchJob(msg.getSource());
				break;
			case FINISHED:
				finish(msg);
				break;
			case HTREMOVE:
				htremove(msg);
				break;
			case HTMIN:
				updateMin((Integer)msg.getAttachment(0));
				break;
			default:
				Debug.println("Received a message: \n" + msg);
				break;
		}
	}
	@Override
	public SocketThread connectionAccepted(Connection newConnection, Socket client)
	{
		SocketThread newClient = super.connectionAccepted(newConnection, client);
		System.out.println("Prime Server accepted new connection: " + newClient);
		//Lookup the new connection in the Clients table.
		Message msg = new Message(MessageType.INITIALIZE, newClient, "Hello from Prime Server");
		msg.attach(numClients);
		msg.attach(maxPrime);
		msg.attach(maxClients);
		newClient.send(msg);
		clientList.add(numClients, newClient);
		numClients++;
		if(numClients >= maxClients)
		{
			startClients();
		}
		return newClient;
	}
	
	public void dispatchJob(Connection destination)
	{
		//Lookup the connection in the Clients table.
		SocketThread client = clients.get(destination);
		dispatchJob(client);
	}
	
	public void dispatchJob(SocketThread client)
	{
		Message msg = null;
		if(lastPrime >= maxPrime)
		{
			msg = new Message(MessageType.FINISHED, client, "All Primes Found");
			client.send(msg);
		}
		else
		{
			msg = new Message(MessageType.RESET, client, "New Job from PrimeServer");
			msg.attach(lastPrime++);
			totalJobs++;
			msg.attach(new Long(deadlineFactor*maxPrime/lastPrime));
			client.send(msg);
		}
	}
	
	public void grantLock(Message msg)
	{
		boolean interrupted = false;
		
		do //Get the semaphore
		{
			interrupted = false;
			try
			{
				semLock.acquire();
			}
			catch (InterruptedException ex)
			{
				interrupted = true;
			}
		} while (interrupted);
		
		
		totalEnergy += (Long)msg.getAttachment(0);
		totalIterations += (Long)msg.getAttachment(1);
		totalClientTime += (Long)msg.getAttachment(2);
		//They missed the deadline
		if((Boolean)msg.getAttachment(3))
		{
			missedDeadlines++;
		}
		Connection requester = msg.getSource();
		Debug.println("" + requester + " has the lock");
		SocketThread client = clients.get(requester);
		msg = new Message(MessageType.LOCK, client, "Lock Granted from Server.");
		client.send(msg);
	}
	
	public void releaseLock()
	{
		System.err.println("" + " lock released");
		semLock.release();
	}
	
	public void startClients()
	{
		Message msg = null;
		System.out.println("\n******Starting Clients*******\n");
		startTime = System.currentTimeMillis();
		for(SocketThread client : clientList)
		{
			dispatchJob(client);
		}
	}
	
	public void htremove(Message msg)
	{
		int clientIndex = (Integer)msg.getAttachment(0);
		clientIndex = clientIndex % numClients;
		SocketThread client = clientList.get(clientIndex);
		msg.updateDestination(client.getRemoteConnection());
		client.send(msg);
	}
	
	synchronized public void finish(Message msg)
	{
		Debug.println("Received final commit: " + msg);
		for(Object i : msg.getAllAttachments())
		{
			result.add((Integer)i);
		}
		if(++clientCommits == maxClients)
		{
			long endTime = System.currentTimeMillis();
			double elapsedTime = (endTime-startTime)/1000.0;
			Debug.println("Final primes list: " + result);
			System.out.println("Elapsed Time: " + elapsedTime);
			System.out.println("Energy Consumed: " + totalEnergy);
			System.out.println("Total Processing Time: " + totalClientTime);
			System.out.println("Iterations: " + totalIterations);
			System.out.println("Deadlines Missed: " + missedDeadlines);
			System.out.println("Total Jobs: " + totalJobs);
			try
			{
				Thread.sleep(2000);
			}
			catch (Exception e)
			{}
			System.exit(0);
		}
	}
	
	public void updateMin(int newMin)
	{
		if(newMin > 2)
		{
			lastPrime = newMin;
			Debug.println("Updating lastPrime to: " + lastPrime);
		}
	}
	
	public static void main(String [] args)
	{
		try
		{
			new PrimeServer(Integer.parseInt(args[0]), Integer.parseInt(args[1]), Integer.parseInt(args[2])).start();
		}
		catch(Exception e)
		{
			Debug.println("PrimeServer.java(192): Could not build Prime Server: " + e);
		}
		Debug.println("Built server");
	}
}